package com.serene.im.config;


import com.serene.im.SpringBeanUtil;
import com.serene.im.bean.BeanUtil;
import com.serene.im.entity.ChatMsg;
import com.serene.im.entity.DataContent;
import com.serene.im.enums.MsgActionEnum;
import com.serene.im.pojo.ImChatMsgLogs;
import com.serene.im.pojo.SysUser;
import com.serene.im.service.ImChatMsgLogsService;
import com.serene.im.service.SysUserService;
import com.serene.im.utils.JsonUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 处理消息的handler
 * TextWebSocketFrame： 在netty中，是用于为websocket专门处理文本的对象，frame是消息的载体
 * SimpleChannelInboundHandler：	对于请求来讲 ，相当于 【入站，入境】
 *
 * @author serene
 * @date 2021/3/18 15:55
 */

public class NettyWsChannelInboundHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    private static final Logger log = LoggerFactory.getLogger(NettyWsChannelInboundHandler.class);

    /**
     * 用于记录和管理所有客户端的channle
     */
    public static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);


//    @Autowired
//    private SysUserService sysUserService;

    /**
     * 从channel缓冲区读数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("8.从channel缓冲区读数据");
        // 获得 channel
        Channel currentChannel = ctx.channel();
        //获取客户端所传输的消息
        String content = msg.text();
        log.info("接收消息: {} ", content);
        dealWithToChatWith(currentChannel, content);
    }

    /**
     * 处理聊天数据
     */
    protected void dealWithToChatWith(Channel currentChannel, String content) throws Exception {
        System.out.println("9.进入获取客户端发来的消息");
        // 1. 获取客户端发来的消息   将json转为对象
        DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
        Integer action = dataContent.getAction();
        System.out.println("类型Id-->" + action);

        /**
         * 2. 判断消息类型，根据不同的类型来处理不同的业务
         */
        //type=1：第一次(或重连)初始化连接
        if (action == MsgActionEnum.CONNECT.type) {
            System.out.println("进入初始化连接");
            // 2.1  当websocket 第一次open的时候，初始化channel，把用的channel和userId关联起来
            String sendId = dataContent.getChatMsg().getSendId();
            UserChannelRel.put(sendId, currentChannel);
            UserChannelRel.output();

            //type=2：聊天消息
        } else if (action == MsgActionEnum.CHAT.type) {
            System.out.println("进入好友聊天消息  type=" + MsgActionEnum.CHAT.type);
            //  2.2  聊天类型的消息，把聊天记录保存到数据库，同时标记消息的签收状态[未签收]
            ImChatMsgLogsService imChatMsgLogsService = SpringBeanUtil.getBean(ImChatMsgLogsService.class);

            ChatMsg chatMsg = dataContent.getChatMsg();
            ImChatMsgLogs logs = new ImChatMsgLogs();
            BeanUtil.copyBeanProp(logs, chatMsg);
            logs.setToType(1);
            Integer msgId = imChatMsgLogsService.saveWebMsgLogs(logs);

            chatMsg.setMsgId(msgId.toString());
            //消息发送时间
            chatMsg.setSendTime(new Date());
            DataContent dataContentMsg = new DataContent();
            dataContentMsg.setChatMsg(chatMsg);

            // 给自己发送成功消息
            Channel sendIdChannel = UserChannelRel.get(chatMsg.getSendId());
            sendIdChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(dataContent)));
            //发送消息 从全局用户Channel关系中获取接受方的channel 判断对方是否在线
            Channel receiverChannel = UserChannelRel.get(chatMsg.getReceiveId());

            //判断用户是否在线
            if (receiverChannel == null) {
                SysUserService sysUserService = SpringBeanUtil.getBean(SysUserService.class);
                SysUser user = sysUserService.queryUserById(chatMsg.getReceiveId());
                // TODO channel为空代表用户离线，推送消息（JPush，个推，小米推送）   添加离线消息记录
                log.info("[" + user.getNickname() + "]" + " 用户离线中 ... receiverChannel 为 null");

                //修改用户为离线状态
                imChatMsgLogsService.updateOfflineStatusTwo(msgId);
            } else {
                //当receiverChannel不为空的时候，从ChannelGroup去查找对应的channel是否存在
                Channel findChannel = users.find(receiverChannel.id());
                if (findChannel != null) {
                    // 用户在线 将对象转换成json字符串
                    receiverChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(dataContent)));
                } else {
                    // 用户离线 TODO 推送消息	 添加离线消息记录
                    log.info(" 用户离线2 ... findChannel 是  null");
                    //修改雍用户为离线状态
                    imChatMsgLogsService.updateOfflineStatusTwo(msgId);
                }
            }

            //type=3：消息签收
        } else if (action == MsgActionEnum.SIGNED.type) {
            System.out.println("进入消息签收");
            log.info("消息通知.....  ");

            // 扩展字段在signed类型的消息中，代表需要去签收的消息id，逗号间隔
            String msgIdsStr = dataContent.getExtand();
            String msgIds[] = msgIdsStr.split(",");

            List<String> msgIdList = new ArrayList<>();
            for (String mid : msgIds) {
                if (StringUtils.isNotBlank(mid)) {
                    msgIdList.add(mid);
                }
            }
            if (msgIdList != null && !msgIdList.isEmpty() && msgIdList.size() > 0) {
                //  2.3  签收消息类型，针对具体的消息进行签收，修改数据库中对应消息的签收状态[已签收]
                ImChatMsgLogsService imChatMsgLogsService = SpringBeanUtil.getBean(ImChatMsgLogsService.class);
                // 批量签收
                imChatMsgLogsService.updateMsgReadStatusOne(msgIdList);
            }

            //type=4：客户端保持心跳
        } else if (action == MsgActionEnum.KEEPALIVE.type) {
            System.out.println("客户端保持心跳");

            //  2.4  心跳类型的消息
            log.info("收到来自channel为[" + currentChannel + "]的心跳包...");

            //type=6：好友申请
        } else if (action == MsgActionEnum.FRIEND_REQUEST.type) {
            // 好友申请
            ChatMsg chatMsg = dataContent.getChatMsg();
            String sendId = chatMsg.getSendId();
            String receiveId = chatMsg.getReceiveId();
            log.info("sendId = " + sendId + ".... 好友申请.... receiveId =" + receiveId);

            //type=7：群消息
        } else if (action == MsgActionEnum.GROUP_MSG.type) {
            System.out.println("进入群消息");
            //群消息发送
            ImChatMsgLogsService imChatMsgLogsService = SpringBeanUtil.getBean(ImChatMsgLogsService.class);

            ChatMsg chatMsg = dataContent.getChatMsg();
            // 保存消息到数据库，并且标记为 未签收
            ImChatMsgLogs logs = new ImChatMsgLogs();
            BeanUtil.copyBeanProp(logs, chatMsg);
            logs.setGroupInfoId(1);
            logs.setToType(2);
            Integer msgId = imChatMsgLogsService.saveWebMsgLogs(logs);
            chatMsg.setMsgId(msgId.toString());
            //消息发送时间
            chatMsg.setSendTime(new Date());

            DataContent dataContentMsg = new DataContent();
            dataContentMsg.setChatMsg(chatMsg);
            // 给所有在线的 im用户 发送信息
            for (Channel c : users) {
                System.out.println("给所有在线的 im用户 发送信息");
                c.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(dataContent)));
            }
            // 更新消息状态为已读
            log.info(" 群消息发送... users.size = " + users.size());
        }
    }

    /**
     * 当客户端连接服务端之后（打开连接）
     * 获取客户端的channle，并且放到ChannelGroup中去进行管理
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        users.add(ctx.channel());
        log.info(" netty 获得连接.....");
        System.out.println("6.netty获取连接");
    }

    /**
     * 移除
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        String channelId = ctx.channel().id().asShortText();
        // 当触发handlerRemoved，ChannelGroup会自动移除对应客户端的channel
        users.remove(ctx.channel());
        log.info("客户端被移除，channelId为：" + channelId);
    }

    /**
     * 连接发送异常
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 发生异常之后关闭连接（关闭channel），随后从ChannelGroup中移除
        ctx.channel().close();
        users.remove(ctx.channel());
        log.info(" netty 异常了...... ");
    }

}
